#include "stdafx.hpp"
#include "ColligereSearchFreeDb.hpp"
#include "ColligereApp.hpp"

col::SearchFreeDb::SearchFreeDb ( const wxString &rDevice )
{
	wxASSERT ( !rDevice.empty() );
	mDiscId.SetDevice ( rDevice );
}

void col::SearchFreeDb::SetDevice ( const wxString &rDevice )
{
	 mDiscId.SetDevice ( rDevice );
}

col::Artist col::SearchFreeDb::DownloadResult ( const SearchResult &rResult )
{
	using namespace boost;

	const wxURI uri ( str ( format ( "%1%/~cddb/cddb.cgi?cmd=cddb+read+%2%+%3%+%4%&proto=6" ) % 
		GetServerAddress() % rResult.GetCategory() % rResult.GetDiscId() % GetServerHello() ) );

	wxString data;
	get_webpage ( uri, data );
	if ( !data.empty() )
	{
		return ParseDatabaseItem ( data );
	}
	return Artist();
}

col::Artist col::SearchFreeDb::ParseDatabaseItem ( const wxString &rFromData )
{
	typedef AudioDatabaseTables t;

	Artist artist;
	Album album;

	wxStringTokenizer tkz ( rFromData, "\n" );
	while ( tkz.HasMoreTokens() )
	{
		wxString token ( tkz.GetNextToken() );
		if ( token[0] == '#' ) continue;

		if ( token.StartsWith ( "DTITLE=" ) )
		{
			/** 
			 * "There may be other "/" characters in the DTITLE, but not 
			 * with space on both sides, as that character sequence is exclusively 
			 * reserved as delimiter of artist and disc title!" - The FreeDb How-To
			 **/
			const int sep_pos = token.find ( " / " );
			wxASSERT ( sep_pos != wxNOT_FOUND );

			artist.SetArtist ( token.substr ( 7, sep_pos - 7 ) );
			album.SetAlbum ( token.substr ( sep_pos + 3 ) );
		}
		else if ( token.StartsWith ( "DYEAR" ) )
		{
			album.SetYear ( token.AfterFirst('=') );
		}
		else if ( token.StartsWith ( "DGENRE" ) )
		{
			album.SetGenre ( token.AfterFirst('=') );
		}
		else if ( token.StartsWith ( "EXTD" ) )
		{
			// There can be multiple EXTDs.
			album.SetExtInfo ( album.GetExtInfo() + wxString('\n') + 
				token.AfterFirst('=') );
		}
		else if ( token.StartsWith ( "TTITLE" ) )
		{
			long num = -1;
			token.substr(6).BeforeFirst('=').ToLong ( &num );
			wxASSERT ( num != -1 );
			Track t;
			// The track numbering is zero-based in freedb.
			t.SetNum ( wxString() << num + 1 );
			t.SetTitle ( token.AfterFirst('=') );
			t.SetLength ( mDiscId.GetTrackLength ( num + 1 ) );
			album.AddChild(t);
		}
	}
	album.SetFormat ( "CD" );
	artist.AddChild ( album );
	return artist;
}

const wxString col::SearchFreeDb::GetServerAddress() const
{
	// According to the FreeDb how-to, this address should be always valid.
	return "http://freedb.freedb.org";
}

col::SearchFreeDb::SearchResultDeque col::SearchFreeDb::Search()
{
	using namespace boost;

	if ( mDiscId.CalculateId() )
	{
		const wxURI uri ( str ( format ( "%1%/~cddb/cddb.cgi?cmd=%2%%3%&proto=6" ) %
			GetServerAddress() % DiscInfoToQuery() % GetServerHello() ) );

		SearchResultDeque results;

		wxString data;
		get_webpage ( uri, data );
		
		const long ret ( ParseSearchResults ( data, results ) );

		if  ( ret < 0 )
		{
			wxLogError ( "Unable to parse search results from the server." );
		}
		else if ( ret == 0 )
		{
			wxLogMessage ( "No results found." );
		}
		return results;
	}
	return SearchResultDeque();
}

const wxString col::SearchFreeDb::GetServerHello() const
{
	return "&hello=usr+foobar.com+Colligere+0.00";
}

long col::SearchFreeDb::ParseSearchResults ( const wxString &rFrom,
											SearchResultDeque &rTo )
{
	// category discid artist / album
	static const wxRegEx sre ( "([a-z]+)\\s(\\w+)\\s(.+)\\s/\\s(.+?)\\s", wxRE_ADVANCED );

	switch ( GetServerResponseCode ( rFrom ) )
	{
	// Unable to parse the response code from the data.
	case -1:
	// Database entry is corrupt.
	case 403:
	// No handshake.
	case 409:
		return -1;
	// Found exact match.
	case 200:
		{			
			if ( sre.Matches ( rFrom ) )
			{
				rTo.push_back ( BuildSearchResultFromRegexMatch ( sre, rFrom ) );
			}
			break;
		}
	// Multiple exact matches.
	case 210:
	// Found inexact matches.
	case 211:
		{
			wxStringTokenizer tkz ( rFrom, "\n" );
			while ( tkz.HasMoreTokens() )
			{
				const wxString token ( tkz.GetNextToken() );
				if ( sre.Matches ( token ) )
				{
					rTo.push_back ( BuildSearchResultFromRegexMatch ( sre, token ) );
				}
			}
			break;
		}
	// No results found.
	case 202:
		break;
	}
	return rTo.size();
}

col::SearchFreeDb::SearchResult col::SearchFreeDb::BuildSearchResultFromRegexMatch
( const wxRegEx &rRe, const wxString &rData )
{
	size_t start = 0, len = 0;

	rRe.GetMatch ( &start, &len, 1 );
	const wxString cat ( rData.substr ( start, len ) );

	rRe.GetMatch ( &start, &len, 2 );
	const wxString discid ( rData.substr ( start, len ) );

	rRe.GetMatch ( &start, &len, 3 );
	const wxString artist ( rData.substr ( start, len ) );

	rRe.GetMatch ( &start, &len, 4 );
	wxString album ( rData.substr ( start, len ) );
	album.Trim();

	return SearchResult ( cat, discid, wxString::Format ( "%s / %s", artist, album ) );
}

long col::SearchFreeDb::GetServerResponseCode ( const wxString &rFromData ) const
{
	long code = -1;
	rFromData.substr ( 0, 3 ).ToLong ( &code );
	return code;
}

const wxString col::SearchFreeDb::DiscInfoToQuery()
{
	using namespace boost;

	wxString q ( str ( format ( "cddb+query+%08X+%d+" ) % mDiscId.GetId() % 
		mDiscId.GetTrackCount() ) );

	foreach ( wxUint32 frame, mDiscId.GetTrackStartFrames() )
	{
		q += str ( format ( "%1%+" ) % frame );
	}
	q += str ( format ( "%1%" ) % mDiscId.GetDiscLengthInSeconds() );
	return q;
}

//////////////////////////////////////
// SearchFreeDb::SearchResult
//////////////////////////////////////

col::SearchFreeDb::SearchResult::SearchResult() 
{ 
}

col::SearchFreeDb::SearchResult::SearchResult( const wxString &rCat, 
											  const wxString &rDiscId,
											  const wxString &rTitle ) 
											  : mCat ( rCat ), mDiscId 
											  ( rDiscId ), mTitle ( rTitle ) 
{ 
}

const wxString &col::SearchFreeDb::SearchResult::GetCategory() const 
{ 
	return mCat; 
}

const wxString &col::SearchFreeDb::SearchResult::GetDiscId() const 
{ 
	return mDiscId; 
}

const wxString &col::SearchFreeDb::SearchResult::GetTitle() const 
{ 
	return mTitle; 
}